const std = @import("std");
const awtk = @import("awtk");
const model_list = @import("model_list.zig");

const c = awtk.c;

pub const Error = error{
    Mismatch,
};

fn isExecFnRetrunType(Type: type) bool {
    return switch (@typeInfo(Type)) {
        .error_union => |error_union| error_union.payload == awtk.Notify,
        else => false,
    };
    //     return Type == anyerror!awtk.Notify;
}

pub fn isExecFn(Model: type, Func: type) bool {
    const TypeInfo = @typeInfo(Func);
    if (TypeInfo != .@"fn") {
        return false;
    }

    const func = TypeInfo.@"fn";
    const params = func.params;

    return switch (params.len) {
        1 => params[0].type == *Model and isExecFnRetrunType(func.return_type.?),
        2 => params[0].type == *Model and params[1].type == ?[:0]const u8 and isExecFnRetrunType(func.return_type.?),
        else => false,
    };
}

pub fn isCanFn(Model: type, Func: type, decl_name: [:0]const u8) bool {
    const TypeInfo = @typeInfo(Func);
    if (TypeInfo != .@"fn") {
        return false;
    }

    if (!std.mem.startsWith(u8, decl_name, "can")) {
        return false;
    }

    const func = TypeInfo.@"fn";
    const params = func.params;

    return switch (params.len) {
        1 => params[0].type == Model and func.return_type == bool,
        2 => params[0].type == Model and params[1].type == ?[:0]const u8 and func.return_type == bool,
        else => false,
    };
}

pub fn isGetPropFn(Type: type, Func: type, decl_name: [:0]const u8) bool {
    const TypeInfo = @typeInfo(Func);
    if (TypeInfo != .@"fn") {
        return false;
    }

    if (!std.mem.startsWith(u8, decl_name, "get")) {
        return false;
    }

    const func = TypeInfo.@"fn";
    const params = func.params;
    return params.len == 3 and
        params[0].type == Type and
        params[1].type == []const u8 and
        params[2].type == *awtk.Value and
        func.return_type == c.ret_t;
}

pub fn isSetPropFn(Type: type, Func: type, decl_name: [:0]const u8) bool {
    const TypeInfo = @typeInfo(Func);
    if (TypeInfo != .@"fn") {
        return false;
    }

    if (!std.mem.startsWith(u8, decl_name, "set")) {
        return false;
    }

    const func = TypeInfo.@"fn";
    const params = func.params;
    return params.len == 3 and
        params[0].type == *Type and
        params[1].type == []const u8 and
        params[2].type == *const awtk.Value and
        func.return_type == c.ret_t;
}

pub fn fnParamCount(Func: type) usize {
    const TypeInfo = @typeInfo(Func);
    if (TypeInfo != .@"fn") {
        return 0;
    }

    return TypeInfo.@"fn".params.len;
}

pub fn fnHasParam(Func: type, comptime index: u8, Type: type) bool {
    const TypeInfo = @typeInfo(Func);
    if (TypeInfo != .@"fn") {
        return false;
    }

    const params = TypeInfo.@"fn".params;
    return params.len > index and params[index].type == Type;
}

pub fn isObject(Type: type) bool {
    const type_name = @typeName(Type);
    const obj_type_name = @typeName(awtk.Object);

    return std.mem.indexOf(u8, type_name, obj_type_name) != null;
}

pub fn objectName(Model: type) [:0]const u8 {
    return "Obj." ++ awtk.baseTypeName(Model);
}

pub fn getFieldProp(path: []const u8, Type: type, value: anytype, v: *awtk.Value) c.ret_t {
    if (comptime std.meta.hasMethod(Type, "getProp")) {
        return value.getProp(path, v);
    }

    var ret: c.ret_t = c.RET_NOT_FOUND;

    const field_name = awtk.frontName(path);
    const sub_path = awtk.subPath(path);
    const TypeInfo = @typeInfo(Type);

    // 匹配虚拟property，即以get开头的成员函数
    inline for (TypeInfo.@"struct".decls) |decl| {
        const a_decl = @field(Type, decl.name);
        const DeclType = @TypeOf(a_decl);

        if (comptime isGetPropFn(Type, DeclType, decl.name)) {
            const vfield_name = decl.name[3..];
            if (std.ascii.toLower(vfield_name[0]) == field_name[0] and std.mem.eql(u8, vfield_name[1..], field_name[1..])) {
                const func = a_decl;
                if (func(value, sub_path, v) == c.RET_OK) {
                    return c.RET_OK;
                }
                break;
            }
        }
    }

    // 匹配field（即成员变量）作为property
    inline for (TypeInfo.@"struct".fields) |field| {
        // 以'_'开头的field为私有
        if (comptime field.name[0] == '_') {
            continue;
        }

        if (std.mem.eql(u8, field.name, field_name)) {
            const field_value = @field(value, field.name);
            ret = getAnyProp(sub_path, field.type, field_value, v);
            if (ret != c.RET_OK) {
                std.log.warn("unsupported type {} for {}.{s} on {s}", .{ field.type, Type, field.name, sub_path });
            }
            break;
        }
    }

    if (ret != c.RET_OK) {
        std.log.warn("{} does not match '{s}' property", .{ Type, path });
    }

    return ret;
}

fn getAnyProp(path: []const u8, Type: type, value: anytype, v: *awtk.Value) c.ret_t {
    //std.log.debug("{s} {}: {s}", .{ @src().fn_name, Type, path });
    if (comptime Type == *awtk.Object) {
        return c.tk_object_get_prop(value, path.ptr, v);
    }

    var ret: c.ret_t = c.RET_OK;
    switch (@typeInfo(Type)) {
        .bool => _ = c.value_set_bool(v, value),
        .int => ret = getIntProp(Type, value, v),
        .float => ret = getFloatProp(Type, value, v),
        .@"struct" => ret = getStructProp(path, Type, value, v),
        .@"enum" => ret = getEnumProp(path, Type, value, v),
        //         .Optional => |Optional| {
        //             if (value) |rvalue| {
        //                 ret = getAnyProp(path, Optional.child, rvalue, v);
        //             } else {
        //                 _ = c.value_set_str(v, "");
        //             }
        //         },
        .pointer => |pointer| ret = getAnyProp(path, pointer.child, value.*, v),
        else => ret = c.RET_NOT_FOUND,
    }

    return ret;
}

fn getIntProp(Type: type, value: anytype, v: *awtk.Value) c.ret_t {
    switch (Type) {
        i8 => _ = c.value_set_int8(v, value),
        u8 => _ = c.value_set_uint8(v, value),
        i16 => _ = c.value_set_int16(v, value),
        u16 => _ = c.value_set_uint16(v, value),
        i32 => _ = c.value_set_int32(v, value),
        u32 => _ = c.value_set_uint32(v, value),
        i64 => _ = c.value_set_int64(v, value),
        u64 => _ = c.value_set_uint64(v, value),
        else => return c.RET_NOT_FOUND,
    }

    //std.log.debug("get {}: {d}", .{ Type, value });
    return c.RET_OK;
}

fn getFloatProp(Type: type, value: anytype, v: *awtk.Value) c.ret_t {
    switch (Type) {
        f32 => _ = c.value_set_float32(v, value),
        f64 => _ = c.value_set_double(v, value),
        else => return c.RET_NOT_FOUND,
    }

    //std.log.debug("get {}: {f}", .{ Type, value });
    return c.RET_OK;
}

fn getStructProp(path: []const u8, Type: type, value: anytype, v: *awtk.Value) c.ret_t {
    //std.log.debug("{s} {}: {s}", .{ @src().fn_name, Type, path });

    return switch (Type) {
        awtk.String => blk: {
            const string = @as(awtk.String, value);
            _ = c.value_set_str(v, string.cstr());
            break :blk c.RET_OK;
        },
        else => getFieldProp(path, Type, value, v),
    };
}

fn getEnumProp(fmt: []const u8, Type: type, value: anytype, v: *awtk.Value) c.ret_t {
    _ = Type;
    //     std.log.debug("{s} {}: {s}", .{ @src().fn_name, Type, fmt });

    if (fmt.len == 0) {
        _ = c.value_set_int32(v, @intFromEnum(value));
    } else if (std.mem.eql(u8, fmt, "str")) {
        _ = c.value_set_str(v, @tagName(value));
    } else {
        return c.RET_NOT_FOUND;
    }

    return c.RET_OK;
}

pub fn setFieldProp(path: []const u8, Type: type, pvalue: anytype, v: *const awtk.Value) c.ret_t {
    if (comptime std.meta.hasMethod(Type, "setProp")) {
        return pvalue.setProp(path, v);
    }

    var ret: c.ret_t = c.RET_NOT_FOUND;

    const field_name = awtk.frontName(path);
    const sub_path = awtk.subPath(path);
    const TypeInfo = @typeInfo(Type);

    // 匹配虚拟property，即以set开头的成员函数
    inline for (TypeInfo.@"struct".decls) |decl| {
        const a_decl = @field(Type, decl.name);
        const DeclType = @TypeOf(a_decl);

        if (comptime isSetPropFn(Type, DeclType, decl.name)) {
            const vfield_name = decl.name[3..];
            if (std.ascii.toLower(vfield_name[0]) == field_name[0] and std.mem.eql(u8, vfield_name[1..], field_name[1..])) {
                const func = a_decl;
                if (func(pvalue, sub_path, v) == c.RET_OK) {
                    return c.RET_OK;
                }
                break;
            }
        }
    }

    inline for (TypeInfo.@"struct".fields) |field| {
        // 以'_'开头的field为私有
        if (comptime field.name[0] == '_') {
            continue;
        }

        if (std.mem.eql(u8, field.name, field_name)) {
            const pfield_value = &@field(pvalue.*, field.name);
            ret = setAnyProp(sub_path, field.type, pfield_value, v);

            switch (ret) {
                c.RET_OK => {},
                c.RET_FAIL => std.log.warn("fail to set {}.{s}", .{ Type, field.name }),
                else => std.log.warn("unsupported type {} for {}.{s}", .{ field.type, Type, field.name }),
            }
            break;
        }
    }

    return ret;
}

fn setAnyProp(path: []const u8, Type: type, pvalue: anytype, v: *const awtk.Value) c.ret_t {
    if (comptime Type == *awtk.Object) {
        return c.tk_object_set_prop(pvalue.*, path.ptr, v);
    }

    var ret: c.ret_t = c.RET_OK;
    switch (@typeInfo(Type)) {
        .bool => pvalue.* = c.value_bool(v),
        .int => ret = setIntProp(Type, pvalue, v),
        .float => ret = setFloatProp(Type, pvalue, v),
        .@"struct" => ret = setStructProp(path, Type, pvalue, v),
        .@"enum" => ret = setEnumProp(Type, pvalue, v),
        //         .Optional => {
        //             if (pvalue) |rpvalue| {
        //                 ret = setAnyProp(path, @TypeOf(rpvalue), rpvalue, v);
        //             } else {
        //                 ret = c.RET_NOT_FOUND;
        //             }
        //         },
        .pointer => |pointer| {
            switch (pointer.size) {
                .one => ret = setAnyProp(path, pointer.child, pvalue.*, v),
                else => ret = c.RET_NOT_FOUND,
            }
        },
        else => ret = c.RET_NOT_FOUND,
    }

    return ret;
}

fn setIntProp(Type: type, pvalue: anytype, v: *const awtk.Value) c.ret_t {
    switch (Type) {
        i8 => pvalue.* = c.value_int8(v),
        u8 => pvalue.* = c.value_uint8(v),
        i16 => pvalue.* = c.value_int16(v),
        u16 => pvalue.* = c.value_uint16(v),
        i32 => pvalue.* = c.value_int32(v),
        u32 => pvalue.* = c.value_uint32(v),
        i64 => pvalue.* = c.value_int64(v),
        u64 => pvalue.* = c.value_uint64(v),
        else => return c.RET_NOT_FOUND,
    }

    //std.log.debug("set {}: {d}", .{ Type, pvalue.* });
    return c.RET_OK;
}

fn setFloatProp(Type: type, pvalue: anytype, v: *const awtk.Value) c.ret_t {
    switch (Type) {
        f32 => pvalue.* = c.value_float32(v),
        f64 => pvalue.* = c.value_double(v),
        else => return c.RET_NOT_FOUND,
    }

    //std.log.debug("set {}: {f}", .{ Type, pvalue.* });
    return c.RET_OK;
}

fn setStructProp(path: []const u8, Type: type, pvalue: anytype, v: *const awtk.Value) c.ret_t {
    return switch (Type) {
        awtk.String => blk: {
            const pstring = @as(*awtk.String, pvalue);
            pstring.setCStr(c.value_str(v)) catch |e| {
                std.log.err("VM setProp {}.{s} error: {}", .{ Type, path, e });
                break :blk c.RET_FAIL;
            };

            break :blk c.RET_OK;
        },
        else => setFieldProp(path, Type, pvalue, v),
    };
}

fn setEnumProp(Type: type, pvalue: anytype, v: *const awtk.Value) c.ret_t {
    if (v.type == c.VALUE_TYPE_STRING) {
        const str = std.mem.sliceTo(c.value_str(v), 0);
        pvalue.* = std.meta.stringToEnum(Type, str) orelse return c.RET_FAIL;
    } else {
        pvalue.* = @enumFromInt(c.value_int32(v));
    }

    return c.RET_OK;
}

pub const VTableOverwrite = extern struct {
    on_destroy: c.tk_object_on_destroy_t = null,
    compare: c.tk_object_compare_t = null,
    get_prop: c.tk_object_get_prop_t = null,
    set_prop: c.tk_object_set_prop_t = null,
    copy_props: c.tk_object_copy_props_t = null,
    remove_prop: c.tk_object_remove_prop_t = null,
    foreach_prop: c.tk_object_foreach_prop_t = null,
    clear_props: c.tk_object_clear_props_t = null,
    find_prop: c.tk_object_find_prop_t = null,
    find_props: c.tk_object_find_props_t = null,
    can_exec: c.tk_object_can_exec_t = null,
    exec: c.tk_object_exec_t = null,
    clone: c.tk_object_clone_t = null,
};

pub fn Object(Model: type, Overwrite: VTableOverwrite) type {
    return GenericObject(awtk.Object, Model, Overwrite);
}

pub fn GenericObject(Base: type, Model: type, Overwrite: VTableOverwrite) type {
    return struct {
        object: Base,
        model: Model,

        const _vtable = awtk.ObjectVTable{
            .type = objectName(Model),
            .desc = objectName(Model),
            .size = @sizeOf(Self),
            .is_collection = false,

            .on_destroy = Overwrite.on_destroy orelse onDestroy,
            .compare = Overwrite.compare,
            .get_prop = Overwrite.get_prop orelse getProp,
            .set_prop = Overwrite.set_prop orelse setProp,
            .remove_prop = Overwrite.remove_prop,
            .foreach_prop = Overwrite.foreach_prop,
            .clear_props = Overwrite.clear_props,
            .find_prop = Overwrite.find_prop,
            .find_props = Overwrite.find_props,
            .can_exec = Overwrite.can_exec orelse canExec,
            .exec = Overwrite.exec orelse exec,
            .clone = Overwrite.clone,
        };

        const Self = @This();

        pub fn VTable() *const awtk.ObjectVTable {
            return &Self._vtable;
        }

        pub fn create() !*awtk.Object {
            const obj = c.tk_object_create(&_vtable) orelse return error.TkOOM;
            const self: *Self = @ptrCast(obj);

            if (comptime std.meta.hasFn(Model, "init")) {
                try self.model.init();
            }

            return obj;
        }

        pub fn cast(cobj: ?*awtk.Object) ?*Self {
            if (cobj) |obj| {
                if (obj.vt) |vt| {
                    if (@as(*const awtk.ObjectVTable, @ptrCast(@alignCast(vt))) == &_vtable) {
                        return @ptrCast(obj);
                    }
                }
            }

            return null;
        }

        pub fn onDestroy(cobj: ?*awtk.Object) callconv(.c) c.ret_t {
            var self = cast(cobj) orelse return c.RET_BAD_PARAMS;
            if (comptime std.meta.hasFn(Model, "deinit")) {
                self.model.deinit();
            }

            return c.RET_OK;
        }

        pub fn getProp(cobj: ?*awtk.Object, cname: ?[*:0]const u8, cv: ?*awtk.Value) callconv(.c) c.ret_t {
            const self = cast(cobj) orelse return c.RET_BAD_PARAMS;
            const name = cname orelse return c.RET_BAD_PARAMS;
            const v = cv orelse return c.RET_BAD_PARAMS;

            //std.log.debug("{} getProp: {s}", .{ Model, rname });

            const sname = std.mem.sliceTo(name, 0);
            return getFieldProp(sname, Model, self.model, v);
        }

        pub fn setProp(cobj: ?*awtk.Object, cname: ?[*:0]const u8, cv: ?*const awtk.Value) callconv(.c) c.ret_t {
            const self = cast(cobj) orelse return c.RET_BAD_PARAMS;
            const name = cname orelse return c.RET_BAD_PARAMS;
            const v = cv orelse return c.RET_BAD_PARAMS;

            std.log.debug("{} setProp: {s}", .{ Model, name });

            const sname = std.mem.sliceTo(name, 0);
            return setFieldProp(sname, Model, &self.model, v);
        }

        pub fn canExec(cobj: ?*awtk.Object, cname: ?[*:0]const u8, cargs: ?[*:0]const u8) callconv(.c) bool {
            const self = cast(cobj) orelse return false;
            const name = cname orelse return false;

            //             std.log.debug("{} canExec {s}", .{ Model, name });

            var ret = false;
            var found = false;
            const ModelInfo = @typeInfo(Model);
            inline for (ModelInfo.@"struct".decls) |decl| {
                const model_decl = @field(Model, decl.name);
                const ModelDecl = @TypeOf(model_decl);

                if (comptime isCanFn(Model, ModelDecl, decl.name)) {
                    const func = model_decl;
                    const Func = ModelDecl;
                    const cmd_name = decl.name[3..];
                    if (awtk.cStrEql(name, cmd_name, true)) {
                        found = true;
                        if (comptime fnParamCount(Func) == 2) {
                            ret = func(self.model, awtk.cStrToSlice(cargs));
                        } else {
                            ret = func(self.model);
                        }
                        break;
                    }
                }
            }

            // 如果没有canCmd函数，但有cmd函数，则该cmd的canExec判定一直为true
            if (!found) {
                inline for (ModelInfo.@"struct".decls) |decl| {
                    const model_decl = @field(Model, decl.name);
                    const ModelDecl = @TypeOf(model_decl);

                    if (comptime isExecFn(Model, ModelDecl)) {
                        if (awtk.cStrEql(name, decl.name, false)) {
                            ret = true;
                            break;
                        }
                    }
                }
            }

            return ret;
        }

        pub fn exec(cobj: ?*awtk.Object, cname: ?[*:0]const u8, cargs: ?[*:0]const u8) callconv(.c) c.ret_t {
            const self = cast(cobj) orelse return c.RET_BAD_PARAMS;
            const name = cname orelse return c.RET_BAD_PARAMS;

            //             std.log.debug("{} exec {s}", .{ Model, name });

            var notify = awtk.Notify.NotImpl;
            const ModelInfo = @typeInfo(Model);
            inline for (ModelInfo.@"struct".decls) |decl| {
                const model_decl = @field(Model, decl.name);
                const ModelDecl = @TypeOf(model_decl);

                if (comptime isExecFn(Model, ModelDecl)) {
                    const func = model_decl;
                    const Func = ModelDecl;
                    if (awtk.cStrEql(name, decl.name, false)) {
                        const ret = if (comptime fnParamCount(Func) == 2)
                            func(&self.model, awtk.cStrToSlice(cargs))
                        else
                            func(&self.model);

                        if (ret) |n| {
                            notify = n;
                        } else |e| {
                            std.log.err("exec {}.{s}(args: {?s}) fail: {}", .{ Model, name, cargs, e });
                            notify = .Fail;
                        }
                        break;
                    }
                }
            }

            return notify.toRet();
        }
    };
}

// NOTE 这个测试代码需要test Compile也链接AWTK才会有效
// test "vtable" {
//     //     try awtk.init(std.testing.allocator);
//     //     defer awtk.deinit();
//     awtk.gallocator = std.testing.allocator;
//
//     const T1 = struct {
//         a: i8 = 0,
//     };
//
//     const T2 = struct {
//         a: i8 = 0,
//     };
//
//     const Obj1 = Object(T1, .{});
//     const Obj2 = Object(T2, .{});
//     const obj1 = try Obj1.create();
//     const obj2 = try Obj2.create();
//
//     try std.testing.expect(Obj1.cast(obj1).? == @as(*Obj1, @ptrCast(obj1)));
//     try std.testing.expect(Obj2.cast(obj2).? == @as(*Obj2, @ptrCast(obj2)));
//     //     _ = awtk.run();
//     //     awtk.quit();
// }
